iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
JavaScript

TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?系列 第 6

第6關:First of Array!TypeScript 高要承認,挨打站穩:找出第一個元素

  • 分享至 

  • xImage
  •  

第6關:First of Array

關卡簡介

Implement a generic First<T> that takes an Array T and returns its first element's type.

實現一個泛型 First<T>,它接受一個數組 T,並返回其第一個元素的類型。

任務說明:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3

接下來,你的任務是讓下面的type cases測試通過:

type cases = [
  Expect<Equal<First<[3, 2, 1]>, 3>>,
  Expect<Equal<First<[() => 123, { a: string }]>, () => 123>>,
  Expect<Equal<First<[]>, never>>,
  Expect<Equal<First<[undefined]>, undefined>>,
]

冒險指南:

在這一關中,我們可以從以下幾個方向來思考:

  1. 如何確保 T 是一個 array ?
  2. 怎麼處理 array 裡面不同類型的元素 ?
  3. 怎麼處理 empty array ?

我們將會用到:

  1. extends:用於條件類型中,檢查一個類型是否符合另一個類型的約束。如果符合,則返回相應的類型;否則返回另一個類型。常用於檢查類型的兼容性。
  2. infer:允許從已知類型中推導出未知的類型。通常與 extends 結合使用,以便在條件滿足時捕獲某個類型的具體信息。
  3. never:一種特殊的類型,表示不會有任何值的類型。通常用於表示函數不會正常返回(如拋出異常),或作為條件類型的默認結果,表示不可能的情況。
extends 在第三關已經介紹過,可以回到第三關參考喔!

通關方式:

解法1:

type First<T extends any[]> = T extends []? never : T[0]

細節分析:

  • T extends any[]:此部分確保 T 必須是一個陣列類型,任何陣列都適用,不限定具體類型。
  • T extends []:這裡使用條件類型檢查 T 是否是一個空陣列。如果是空陣列,就返回 never,表示沒有元素。
  • T[0]:如果 T 不是空陣列,則返回陣列的第一個元素類型。T[0] 是 TypeScript 用來取得陣列第一個元素的標準方式。

解法2:

type First<T extends any[]> = T['length'] extends 0? never : T[0]

細節分析:

  • T['length']T['length'] 取得陣列的長度。這是一種靜態檢查陣列是否為空的方式。這裡我們通過訪問 Tlength 屬性來獲取數組的長度。TypeScript 的數組類型都有一個隱式的 length 屬性,這表示數組中元素的數量。

  • T['length'] extends 0:這裡檢查陣列長度是否為 0。如果是,則返回 never,表示空陣列。

  • T[0]:當陣列不為空時,返回第一個元素的類型。這是用來獲取非空陣列第一個元素的標準方式。

解法3:

type First<T extends any[]> = T extends {length:0} ? never : T[0]

細節分析:

  • T extends { length: 0 }:使用條件類型來檢查 T 是否是一個長度為 0 的陣列。透過直接檢查 T 是否符合具有 length 屬性且該屬性為 0 的結構。如果長度為 0,返回 never

  • T[0]:和之前的解法類似,當陣列不為空時,直接返回第一個元素的類型。

解法4:

type First<T extends any[]> = T extends [infer P, ...any[]] ? P : never

細節分析:

  • infer P: 使用 infer 關鍵字從元組的第一個元素中提取類型 P。這樣可以靈活地獲取數組中的第一個元素。
  • ...any[]: 表示剩餘的元素可以是任何類型,這樣可以匹配任何長度的數組。
  • P:如果 T 是一個包含元素的數組,返回第一個元素的類型;否則返回 never。

這樣,我們就能順利通過測試啦 🎉 😭 🎉

關鍵字補給:

  • infer (“Inferring” Within Conditional Types):
    • 介紹:
      The infer keyword in TypeScript allows you to declare a type variable within a conditional type. When used in the context of a conditional type, infer tells TypeScript to:
      1. infer the type of a particular type parameter
      2. assign it to the declared type variable
    • 範例:
      type ElementType<T> = T extends (infer E)[] ? E : never;
      
      • In this type definition, infer E tells TypeScript to infer the type of the array element and assign it to the type variable E.
      • When T matches the pattern (infer E)[], TypeScript infers the type of E based on the type of elements in the array T.
      type ArrayType1 = ElementType<number[]>; // ArrayType1 is number
      type ArrayType2 = ElementType<string[]>; // ArrayType2 is string
      
      • In these examples, ElementType<number[]> matches the pattern (infer E)[], where E is inferred as number. Similarly, ElementType<string[]> infers E as string.
  • never:
    • 介紹:
      The never type represents the type of values that never occur. For instance, never is the return type for a function expression or an arrow function expression that always throws an exception or one that never returns. Variables also acquire the type never when narrowed by any type guards that can never be true.
    • 範例:
      Basic Example:
      function format(value: string | number) {
      if (typeof value === 'string') {
          return value.trim();
      } else {
          return value.toFixed(2); // we're sure it's number
      }
      
      // not a string or number
      // "value" can't occur here, so it's type "never"
      }
      
      The return type of this function is never, indicating that it never produces a normal value. Instead, it always throws an exception, which prevents the function from reaching its end point:
      // Function returning never must not have a reachable end point
      function error(message: string): never {
        throw new Error(message);
      }
      
      const reportError = function () {
          throw Error('my error');
      }
      // reportError's type is () => never.
      
      • Since error function has a return type of never, the return type of fail function is inferred to be never as well.
      • Even though fail function itself doesn't explicitly throw an error, it calls error, which is a never returning function:
      // Inferred return type is never
      function fail() {
        return error("Something failed");
      }
      
      • This function contains an infinite loop (while (true) {}), which means it never terminates normally.
      • The return type of this function is never, indicating that it never produces a normal value. Instead, it continues running indefinitely.
      • Since the loop condition is always true, the loop will never exit, and the function will never reach its end point:
       // Function returning never must not have a reachable end point
      function infiniteLoop(): never {
        while (true) {}
      }
      
      const loop = function () {
          while(true) {}
      }
      // loop's type is () => never.
      

總結:

本次介紹了 First of Array 的實作,下一關會挑戰 Length of Tuple,期待再相見!


上一篇
第5關:Tuple to Object!TypeScript 魔法現眼前:元組變物件
下一篇
第7關:Length of Tuple!TypeScript的三長兩短,給它量,很放心
系列文
TypeScript Type Challenges 冒險篇章:30 天闖關之旅,type 簡單了?你確定?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言